#include <FL/gl.h>
#include <FL/Fl.H>
#include <Fl/Fl_File_Chooser.H>
#include <GL/glu.h>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include "model_display.h"
#include "d_Light.h"
#include "Image.h"
#include "PADSObject.h"

using namespace std;
using namespace A48;
static bool first_time = true;


ModelDisplay::ModelDisplay(int x, int y, int w, int h, char *l) : Fl_Gl_Window(x,y,w,h,l)
{
	//loadMesh("b10k.ply");
	has_texture=false;
	splat_size = 0.2;
	zoom_factor = 1;
	dont_draw=false;
	draw_patch = -1;
	camera = new d_Camera();
	wireframe = false;
	draw_cameras=false;
	draw_axis = true;
	look_through =-1;
	hAng = vAng =0;
	mesh = NULL;
	pads_mesh = NULL;
}

void ModelDisplay::get_camera_center()
{
	if(pads_mesh)
		object_center = pads_mesh->get_center()*(-1);
}

void ModelDisplay::resetTrans()
{
	hAng=vAng=xPan=yPan=0;
}
//GLfloat light2_position[3];

void ModelDisplay::draw()
{
	if(!valid())
	{
		glEnable(GL_SMOOTH);
		glEnable(GL_POLYGON_OFFSET_FILL);
		glPolygonOffset(-1, -2);
		glEnable(GL_DEPTH);
		glEnable(GL_DEPTH_TEST);

		camera->setDimensions(640,480);
		camera->setDirection(1,0,0);
		camera->setPerspective(45);
		camera->setPosition(-50,0,0);
		camera->setViewport(0,0,640,480);
		camera->addLight(GL_LIGHT1);

		camera->applyViewport();
		camera->applyPerspective();
		camera->applyModelview();
		glClearColor(0.5,0.5,0.5,1);
	}
	if(!dont_draw)
	{
		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
		glClearColor(0.5,0.5,0.5,1);
		//int x = Fl::event_x();
		//int y = Fl::event_y();
		//if(!first_time)
		//camera->applyDrag(x,y);
		if(look_through !=-1)
		{
			camera->setDirection(cameras[look_through]->za[0],cameras[look_through]->za[1],cameras[look_through]->za[2]);
			camera->setPerspective(cameras[look_through]->fov);
			camera->setPosition(cameras[look_through]->vp[0], cameras[look_through]->vp[1], cameras[look_through]->vp[2]);
		
			camera->applyPerspective();
			camera->applyModelview();
		}
		glPushMatrix();
			glTranslatef(0,yPan, xPan);
			glScalef(zoom_factor, zoom_factor, zoom_factor);
			glRotatef(hAng, 0, 1, 0);
			glRotatef(vAng, 1, 0, 0);
			if(draw_axis)
				drawAxis();
			if(draw_cameras)
			{
				glPushMatrix();
				glTranslatef(object_center[0],object_center[1],object_center[2]);
				drawCameras();
				glPopMatrix();
			}
			if(pads_mesh)
			{
				glPushMatrix();
				glTranslatef(object_center[0],object_center[1],object_center[2]);
				//glEnable(GL_LIGHTING);
				pads_mesh->draw();
				//glDisable(GL_LIGHTING);
				glPopMatrix();
			}
			if(mesh)
			{
				if(wireframe)
					glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
				else
					glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
				if(draw_patch==-1)
				{
					glEnable(GL_LIGHTING);
					glPushMatrix();
					glTranslatef(10,0,75);
					drawMesh();
					glPopMatrix();
					glDisable(GL_LIGHTING);
				}
				else
				{
					//glEnable(GL_LIGHTING);
					//glClearColor(0,0,0,1);
					drawPatch(draw_patch);
					//glDisable(GL_LIGHTING);
				}
			}
		glPopMatrix();
	}
}

void ModelDisplay::drawAxis()
{
	glBegin(GL_LINES);
		glColor3f(1,0,0);
		glVertex3f(0,0,0);
		glVertex3f(10,0,0);
		glColor3f(0,1,0);
		glVertex3f(0,0,0);
		glVertex3f(0,10,0);
		glColor3f(0,0,1);
		glVertex3f(0,0,0);
		glVertex3f(0,0,10);
	glEnd();
}

Vec3 ModelDisplay::faceNormal(Face *f)
{
	Vec3 n, v0, v1, v2;
	v0 = f->vertex(0)->a.g;
	v1 = f->vertex(1)->a.g;
	v2 = f->vertex(2)->a.g;
	n = (v0 - v1) ^ (v1 - v2);
	return n/norm(n);
}

void ModelDisplay::loadMesh(char *file)
{
	mesh = new Mesh();
	mesh->read_ply(file);
	//float num_faces = mesh->num_faces();
	//std::cout << "Faces: " << num_faces << std::endl;
    if (mesh->num_verts() == 0)
      return;

    for (FaceIter f = mesh->faces_begin(); f != mesh->faces_end(); f++)
    {
      Vec3 n = faceNormal(*f);
      for (int i=0; i<3; i++)
      {
        ((*f)->vertex(i))->a.n += n;
      }
    }
    for (VertexIter vi = mesh->verts_begin(); vi != mesh->verts_end(); vi++)
    {
      unitize((*vi)->a.n);
    }
}

void ModelDisplay::drawMesh()
{
	if(has_texture)
	{
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, texture);
	}
    glBegin(GL_TRIANGLES);
	for(FaceIter f = mesh->faces_begin(); f != mesh->faces_end(); f++)
	{
		glColor3f(1.0, 1.0, 1.0);

		for(int i=0; i < 3; i++)
		{
			glColor3f(1,1,1);
           // double xCoord = ((*f)->coordText[i][X])/(100);//textureWidth);
           // double yCoord = ((*f)->coordText[i][Y])/(100);//textureHeight);
			//cout << xCoord << yCoord << endl;
			if(has_texture)
				glTexCoord2f((*f)->coordText[i][X], (*f)->coordText[i][Y]);
			glNormal3d((*f)->vertex(i)->a.n[X], (*f)->vertex(i)->a.n[Y], (*f)->vertex(i)->a.n[Z]);
			glVertex3d((*f)->vertex(i)->a.g[X], (*f)->vertex(i)->a.g[Y], (*f)->vertex(i)->a.g[Z]);
		}
	}
	glEnd();
	glDisable(GL_TEXTURE_2D);
}

void ModelDisplay::drawPatch(int index)
{
	if(has_texture)
	{
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, texture);
	}
	 glBegin(GL_TRIANGLES);

	 for(PatchIter p = mesh->patches_begin() ; p!=mesh->patches_end(); p++)
	 {
		 if((*p)->get_index()==index)
		 {
			 for(FaceIter f=(*p)->faces_patch_begin(); f!=(*p)->faces_patch_end(); f++)
			 {
				 glColor3f(1.0, 1.0, 1.0);
				 for(int i=0;i<3;i++)
				 {
					 if(has_texture)
						 glTexCoord2f((*f)->coordText[i][X], (*f)->coordText[i][Y]);
					 glNormal3d((*f)->vertex(i)->a.n[X], (*f)->vertex(i)->a.n[Y], (*f)->vertex(i)->a.n[Z]);
					 glVertex3d((*f)->vertex(i)->a.g[X], (*f)->vertex(i)->a.g[Y], (*f)->vertex(i)->a.g[Z]);
				 }
			 }
		 }
	 }
	glEnd();
	glDisable(GL_TEXTURE_2D);
}

void ModelDisplay::drawCameras()
{
	glBegin(GL_LINES);
	for(int i=0; i<num_cams; i++)
	{
		glColor3f(0,0,1);
		glVertex3f(cameras[i]->vp[0], cameras[i]->vp[1], cameras[i]->vp[2]);
		glVertex3f(cameras[i]->za[0]*5+cameras[i]->vp[0], 
								cameras[i]->za[1]*5+cameras[i]->vp[1], 
								cameras[i]->za[2]*5+cameras[i]->vp[2]);
		glVertex3f(cameras[i]->vp[0], cameras[i]->vp[1], cameras[i]->vp[2]);
		glVertex3f(cameras[i]->xa[0]*5+cameras[i]->vp[0], 
								cameras[i]->xa[1]*5+cameras[i]->vp[1], 
								cameras[i]->xa[2]*5+cameras[i]->vp[2]);
		glVertex3f(cameras[i]->vp[0], cameras[i]->vp[1], cameras[i]->vp[2]);
		glVertex3f(cameras[i]->ya[0]*5+cameras[i]->vp[0], 
								cameras[i]->ya[1]*5+cameras[i]->vp[1], 
								cameras[i]->ya[2]*5+cameras[i]->vp[2]);
	}
	glEnd();
}

static char *dirname(char *path)
{
   static char buffer[260];
   size_t len;
   if (path==NULL) {
       strcpy(buffer, ".");
       return buffer;
   }
   len = strlen(path);
   assert(len<sizeof(buffer));
   if (len!=0 && (path[len-1]=='/' || path[len-1]=='\\')) {
       --len;
   }
   while (len!=0 && path[len-1]!='/' && path[len-1]!='\\') {
       --len;
   }
   if (len==0) {
       strcpy(buffer, ".");
   } else if (len==1) {
       if (path[0]=='/' || path[0]=='\\') {
           strcpy(buffer, "/");
       } else {
           strcpy(buffer, ".");
       }
   } else {
       memcpy(buffer, path, len-1);
   }
   return buffer;
}

void ModelDisplay::loadFile()
{
	Fl_File_Chooser *fc;
	fc = new Fl_File_Chooser(".", "PLY Model (*.wvr)\t"
												"PADS Model (*.pads)", Fl_File_Chooser::SINGLE, "Open file");
	fc->show();

	while(fc->visible())
		Fl::wait();

	filename = (char *) fc->value();
	//this->filename = (char *) filename;
	chdir(dirname((char *)filename));

	if(strstr(filename, ".wvr"))
	{
		FILE *wvr_file = fopen((char *)filename, "r");
		fscanf(wvr_file, "Weaver script file v2.0\n");
	
		char *ply_model;
		ply_model = (char *) malloc(50*sizeof(char));
		fscanf(wvr_file, "%s\n", ply_model);
		//std::cout << ply_model << std::endl;
		//this->filename = ply_model;
	
		fscanf(wvr_file, "%d\n", &num_cams);
		//std::cout << num_cams << std::endl;
	
		cam_files = (char **) malloc ( num_cams * sizeof(char *));
		img_files = (char **) malloc ( num_cams * sizeof(char *));
		cameras = (VCGCamera **) malloc (num_cams*sizeof(VCGCamera *));
	
		for(int i=0; i<num_cams; i++)
		{
			cam_files[i] = (char *) malloc (50 * sizeof(char));
			img_files[i] = (char *) malloc (50 * sizeof(char));
			cameras[i] = (VCGCamera *) malloc ( sizeof(VCGCamera));
			fscanf(wvr_file, "%s\n", cam_files[i]);
			fscanf(wvr_file, "%s\n", img_files[i]);
			//std::cout << cam_files[i] << std::endl;
			//std::cout << img_files[i] << std::endl;
		}
	
		patch_file = (char *) malloc (50 * sizeof(char));
		texture_file = (char *) malloc (50 * sizeof(char));
		char *aux = (char *) malloc(1*sizeof(char));
		fscanf(wvr_file,"%c", aux);
	
		  if (aux != NULL)
		  {
			  if (aux[0] == '#')
			  {
				  char *tmp = new char[50];
				  fscanf(wvr_file,"%s\n", tmp);
				  strcpy(patch_file, tmp);
				  strcat(patch_file, "-PTC.ply");

				  strcpy(texture_file, tmp);
				  //texture_file = tmp;
				   //ply_model = tmp;
				 strcat(texture_file, "-WV.ply");
				   char *text_file = new char[50];
				   strcpy(text_file, tmp);
				   strcat(text_file, "-tex.bmp");
				   load_texture(text_file);
				//  strcat(color_file, "-COL.ply");

			  }
			  else
			  {
				  patch_file = NULL;
				  texture_file = NULL;
				  color_file = NULL;
			  }
		  }
		  delete aux;
		fclose(wvr_file);

		//char *tmpfilename;
		if(texture_file)
			this->filename=texture_file;
			//tmpfilename = texture_file;
		else
			//tmpfilename = ply_model;
			this->filename=ply_model;
		if (mesh) 
			delete mesh;
		loadMesh(this->filename);
	
		for(int i=0; i<num_cams; i++)
		{
			cameras[i] = read_ply_cam(cam_files[i], cameras[i]);
			cameras[i]->color = Vec3(1,0,0);
			cameras[i]->xa = -cameras[i]->xa;
			cameras[i]->fov =(2.0*atan(0.5 * cameras[i]->vy/(cameras[i]->focal / cameras[i]->sx)))*180/M_PI;
			cameras[i]->aspect = (double)(cameras[i]->vx)/(double)(cameras[i]->vy);
		}
	
		if(patch_file!=NULL)
		{
			mesh->read_ply_patches(patch_file);
			num_patches = mesh->num_patches();
		}
	}
	if(strstr(filename,".pads"))
	{
		pads_mesh = new PADSMesh((char *) filename);
		std::cout << filename << std::endl;
	}
	redraw();
}

int ModelDisplay::handle(int event)
{
	//int key = Fl::event_key();
	//int button = Fl::event_button();
	int x = Fl::event_x();
	int y = Fl::event_y();

	switch(event)
	{
	case FL_PUSH:
		//std::cout << "push" << std::endl;
		//camera->setDrag(x, y);
		if(first_time)
		{
			camera->mouseClick(D_RIGHT, x, y);
			first_time=false;
			return 1;
			//break;
		}
	//case FL_RELEASE:
	//	if(!first_time)
	//	{
	//		first_time = true;
	//	}
	case FL_DRAG:
		camera->mouseDrag(x, y);
		//std::cout << "drag" << std::endl;
		//std::cout << x << " , " << y << std::endl;
		//break;
		//if(!valid())
		//camera->applyDrag(x,y);
		redraw();
		return 1;
	default:
		return Fl_Gl_Window::handle(event);
	}
}

void ModelDisplay::take_screenshot(char *screen_file)
{
  unsigned char *rgb;
  float fx = 1, fy = 1;
  int x = 0, y = 0, w = camera->getW(), h = camera->getH();

  printf("reading Image buffer\n");

  rgb=(unsigned char*)malloc(w*h*3);
  glPixelZoom(fx,fy);
  glPixelStoref (GL_PACK_ALIGNMENT, 1);
  glReadPixels (x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, (void *)(rgb));

  Image out_img(w, h);
  for (int i = h-1; i >=0; i-- ) {
    for (int j = 0; j < w; j++ ) {
      unsigned char r, g, b;
      r = rgb[(j*3)+(i*w*3)];
      g = rgb[(j*3)+(i*w*3)+1];
      b = rgb[(j*3)+(i*w*3)+2];
      out_img.SetRGB(j,i,r,g,b);
    }
  }
  delete rgb;
  out_img.Save(0, screen_file);
}

void ModelDisplay::load_texture(char *filename)
{
	Image *text = new Image();
	text->Load(filename);
	if(text==NULL)
		return;
    if ( (text->GetW() & (text->GetW() - 1)) != 0 ) {
		printf("warning: image.bmp's width is not a power of 2\n");
	}
	
	// Also check if the height is a power of 2
	if ( (text->GetH() & (text->GetH() - 1)) != 0 ) {
		printf("warning: image.bmp's height is not a power of 2\n");
	}
	unsigned char *rgb;
	text->GetRGB(&rgb);
	
	glEnable(GL_TEXTURE_2D);
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, text->GetW(), text->GetH(), 0, GL_RGB, GL_UNSIGNED_BYTE, rgb);

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	//glDisable(GL_TEXTURE_2D);
	has_texture = true;
	delete text;
}

void ModelDisplay::generateDS()
{
	Patch  *tmp;
	int bbw[num_patches], bbh[num_patches];
	int bbx[num_patches], bby[num_patches];
	float dist[num_patches];
	float dx[num_patches];

	char stringtmp[100];
	sprintf(stringtmp, "mkdir output_pads");
	system(stringtmp);
	chdir("output_pads");

	for(PatchIter p = mesh->patches_begin() ; p!=mesh->patches_end(); p++)
	 {
		 tmp = (*p);
		 PADSNode *node = new PADSNode(cameras[tmp->get_camera_index()], tmp, this, mesh);
		 bbw[tmp->get_index()] = node->getBB_W();
		 bbh[tmp->get_index()] = node->getBB_H();
		 bbx[tmp->get_index()] = node->getBB_X();
		 bby[tmp->get_index()] = node->getBB_Y();
		 dist[tmp->get_index()] = node->getDist();
		 dx[tmp->get_index()] = node->getDx();
	 }
	 
	//printf("%s\n", filename);
	
	ofstream output_pads("pads");
	output_pads << "PADS Format" << endl;
	output_pads << "patches " << num_patches << endl;
	output_pads << "cameras " << num_cams << endl;
	for(int i=0; i<num_cams; i++)
	{
		float cx = cameras[i]->cx;
		float cy = cameras[i]->cy;
		float sx = cameras[i]->sx;
		float sy = cameras[i]->sy;
		float focal = cameras[i]->focal;
		Vec3 p = cameras[i]->vp;
		Vec3 d = cameras[i]->za;
		Vec3 xa = cameras[i]->xa;
		Vec3 ya = cameras[i]->ya;
		output_pads << p[0] << " " << p[1] << " " << p[2] << endl;
		output_pads << xa[0] << " " << xa[1] << " " << xa[2] << endl;
		output_pads << ya[0] << " " << ya[1] << " " << ya[2] << endl;
		output_pads << d[0] << " " << d[1] << " " << d[2] << endl;
		output_pads << sx << " " << sy << " " << cx << " " << cy << " "<< focal << endl;
	}
	for(PatchIter p = mesh->patches_begin() ; p!=mesh->patches_end(); p++)
	 {
		output_pads << (*p)->get_index() << " " << (*p)->get_camera_index() << endl;
		output_pads << bbw[(*p)->get_index()] << " " << bbh[(*p)->get_index()] << " " << bbx[(*p)->get_index()] << " " << bby[(*p)->get_index()] << endl;
		output_pads << dist[(*p)->get_index()] << " " << dx[(*p)->get_index()] << endl;
	 }
	output_pads.close();
	//delete tmp;
}

